/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Internal format query tests
 *//*--------------------------------------------------------------------*/

#include "es31fInternalFormatQueryTests.hpp"
#include "tcuTestLog.hpp"
#include "gluRenderContext.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

class FormatSamplesCase : public TestCase
{
public:
	enum FormatType
	{
		FORMAT_COLOR,
		FORMAT_INT,
		FORMAT_DEPTH_STENCIL
	};

						FormatSamplesCase	(Context& ctx, const char* name, const char* desc, glw::GLenum texTarget, glw::GLenum internalFormat, FormatType type);
private:
	void				init				(void);
	IterateResult		iterate				(void);

	const glw::GLenum	m_target;
	const glw::GLenum	m_internalFormat;
	const FormatType	m_type;
};

FormatSamplesCase::FormatSamplesCase (Context& ctx, const char* name, const char* desc, glw::GLenum target, glw::GLenum internalFormat, FormatType type)
	: TestCase			(ctx, name, desc)
	, m_target			(target)
	, m_internalFormat	(internalFormat)
	, m_type			(type)
{
	DE_ASSERT(m_target == GL_TEXTURE_2D_MULTISAMPLE			||
			  m_target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY	||
			  m_target == GL_RENDERBUFFER);
}

void FormatSamplesCase::init (void)
{
	const bool	isTextureTarget	=	(m_target == GL_TEXTURE_2D_MULTISAMPLE) ||
									(m_target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY);
	auto		ctxType			= m_context.getRenderContext().getType();
	const bool	isES32orGL45	= glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
								  glu::contextSupports(ctxType, glu::ApiType::core(4, 5));

	if (!isES32orGL45 && m_target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
		TCU_THROW(NotSupportedError, "Test requires OES_texture_storage_multisample_2d_array extension or a context version equal or higher than 3.2");

	// stencil8 textures are not supported without GL_OES_texture_stencil8 extension
	if (!isES32orGL45 && isTextureTarget && m_internalFormat == GL_STENCIL_INDEX8 && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_stencil8"))
		TCU_THROW(NotSupportedError, "Test requires GL_OES_texture_stencil8 extension or a context version equal or higher than 3.2");
}

FormatSamplesCase::IterateResult FormatSamplesCase::iterate (void)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	bool					isFloatFormat	= false;
	bool					error			= false;
	glw::GLint				maxSamples		= 0;
	glw::GLint				numSampleCounts	= 0;
	auto					ctxType			= m_context.getRenderContext().getType();
	const bool				isES32orGL45	= glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
											  glu::contextSupports(ctxType, glu::ApiType::core(4, 5));

	if (!isES32orGL45)
	{
		if (m_internalFormat == GL_RGBA16F || m_internalFormat == GL_R32F || m_internalFormat == GL_RG32F || m_internalFormat == GL_RGBA32F || m_internalFormat == GL_R16F || m_internalFormat == GL_RG16F || m_internalFormat == GL_R11F_G11F_B10F)
		{
			TCU_THROW(NotSupportedError, "The internal format is not supported in a context lower than 3.2");
		}
	}
	else if (m_internalFormat == GL_RGBA16F || m_internalFormat == GL_R32F || m_internalFormat == GL_RG32F || m_internalFormat == GL_RGBA32F)
	{
		isFloatFormat = true;
	}

	// Lowest limit
	{
		const glw::GLenum samplesEnum = (m_type == FORMAT_COLOR) ? (GL_MAX_COLOR_TEXTURE_SAMPLES) : (m_type == FORMAT_INT) ? (GL_MAX_INTEGER_SAMPLES) : (GL_MAX_DEPTH_TEXTURE_SAMPLES);
		m_testCtx.getLog() << tcu::TestLog::Message << "Format must support sample count of " << glu::getGettableStateStr(samplesEnum) << tcu::TestLog::EndMessage;

		gl.getIntegerv(samplesEnum, &maxSamples);
		GLU_EXPECT_NO_ERROR(gl.getError(), "get MAX_*_SAMPLES");

		m_testCtx.getLog() << tcu::TestLog::Message << glu::getGettableStateStr(samplesEnum) << " = " << maxSamples << tcu::TestLog::EndMessage;

		if (maxSamples < 1)
		{
			m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: minimum value of "  << glu::getGettableStateStr(samplesEnum) << " is 1" << tcu::TestLog::EndMessage;
			error = true;
		}
	}

	// Number of sample counts
	{
		gl.getInternalformativ(m_target, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts);
		GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_NUM_SAMPLE_COUNTS");

		m_testCtx.getLog() << tcu::TestLog::Message << "GL_NUM_SAMPLE_COUNTS = " << numSampleCounts << tcu::TestLog::EndMessage;

		if (!isFloatFormat)
		{
			if (numSampleCounts < 1)
			{
				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Format MUST support some multisample configuration, got GL_NUM_SAMPLE_COUNTS = " << numSampleCounts << tcu::TestLog::EndMessage;
				error = true;
			}
		}
	}

	// Sample counts
	{
		tcu::MessageBuilder		samplesMsg	(&m_testCtx.getLog());
		std::vector<glw::GLint>	samples		(numSampleCounts > 0 ? numSampleCounts : 1);

		if (numSampleCounts > 0 || isFloatFormat)
		{
			gl.getInternalformativ(m_target, m_internalFormat, GL_SAMPLES, numSampleCounts, &samples[0]);
			GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_SAMPLES");
		}
		else
			TCU_FAIL("glGetInternalFormativ() reported 0 supported sample counts");

		// make a pretty log

		samplesMsg << "GL_SAMPLES = [";
		for (size_t ndx = 0; ndx < samples.size(); ++ndx)
		{
			if (ndx)
				samplesMsg << ", ";
			samplesMsg << samples[ndx];
		}
		samplesMsg << "]" << tcu::TestLog::EndMessage;

		// Samples are in order
		for (size_t ndx = 1; ndx < samples.size(); ++ndx)
		{
			if (samples[ndx-1] <= samples[ndx])
			{
				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Samples must be ordered descending." << tcu::TestLog::EndMessage;
				error = true;
				break;
			}
		}

		// samples are positive
		for (size_t ndx = 1; ndx < samples.size(); ++ndx)
		{
			if (samples[ndx-1] <= 0)
			{
				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Only positive SAMPLES allowed." << tcu::TestLog::EndMessage;
				error = true;
				break;
			}
		}

		// maxSamples must be supported
		if (!isFloatFormat)
		{
			if (samples[0] < maxSamples)
			{
				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: MAX_*_SAMPLES must be supported." << tcu::TestLog::EndMessage;
				error = true;
			}
		}
	}

	// Result
	if (!error)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid value");

	return STOP;
}

class NumSampleCountsBufferCase : public TestCase
{
public:
					NumSampleCountsBufferCase	(Context& ctx, const char* name, const char* desc);

private:
	IterateResult	iterate						(void);
};

NumSampleCountsBufferCase::NumSampleCountsBufferCase (Context& ctx, const char* name, const char* desc)
	: TestCase(ctx, name, desc)
{
}

NumSampleCountsBufferCase::IterateResult NumSampleCountsBufferCase::iterate (void)
{
	const glw::GLint		defaultValue	= -123; // queries always return positive values
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	bool					error			= false;

	// Query to larger buffer
	{
		glw::GLint buffer[2] = { defaultValue, defaultValue };

		m_testCtx.getLog() << tcu::TestLog::Message << "Querying GL_NUM_SAMPLE_COUNTS to larger-than-needed buffer." << tcu::TestLog::EndMessage;
		gl.getInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 2, buffer);
		GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_NUM_SAMPLE_COUNTS");

		if (buffer[1] != defaultValue)
		{
			m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: trailing values were modified." << tcu::TestLog::EndMessage;
			error = true;
		}
	}

	// Query to empty buffer
	{
		glw::GLint buffer[1] = { defaultValue };

		m_testCtx.getLog() << tcu::TestLog::Message << "Querying GL_NUM_SAMPLE_COUNTS to zero-sized buffer." << tcu::TestLog::EndMessage;
		gl.getInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 0, buffer);
		GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_NUM_SAMPLE_COUNTS");

		if (buffer[0] != defaultValue)
		{
			m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Query wrote over buffer bounds." << tcu::TestLog::EndMessage;
			error = true;
		}
	}

	// Result
	if (!error)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected buffer modification");

	return STOP;
}

class SamplesBufferCase : public TestCase
{
public:
					SamplesBufferCase	(Context& ctx, const char* name, const char* desc);

private:
	IterateResult	iterate				(void);
};

SamplesBufferCase::SamplesBufferCase (Context& ctx, const char* name, const char* desc)
	: TestCase(ctx, name, desc)
{
}

SamplesBufferCase::IterateResult SamplesBufferCase::iterate (void)
{
	const glw::GLint		defaultValue	= -123; // queries always return positive values
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	bool					error			= false;

	glw::GLint				numSampleCounts	= 0;

	// Number of sample counts
	{
		gl.getInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts);
		GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_NUM_SAMPLE_COUNTS");

		m_testCtx.getLog() << tcu::TestLog::Message << "GL_NUM_SAMPLE_COUNTS = " << numSampleCounts << tcu::TestLog::EndMessage;
	}

	if (numSampleCounts < 1)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Format MUST support some multisample configuration, got GL_NUM_SAMPLE_COUNTS = " << numSampleCounts << tcu::TestLog::EndMessage;
		error = true;
	}
	else
	{
		// Query to larger buffer
		{
			std::vector<glw::GLint> buffer(numSampleCounts + 1, defaultValue);

			m_testCtx.getLog() << tcu::TestLog::Message << "Querying GL_SAMPLES to larger-than-needed buffer." << tcu::TestLog::EndMessage;
			gl.getInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_SAMPLES, (glw::GLsizei)buffer.size(), &buffer[0]);
			GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_SAMPLES");

			if (buffer.back() != defaultValue)
			{
				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: trailing value was modified." << tcu::TestLog::EndMessage;
				error = true;
			}
		}

		// Query to smaller buffer
		if (numSampleCounts > 2)
		{
			glw::GLint buffer[3] = { defaultValue, defaultValue, defaultValue };

			m_testCtx.getLog() << tcu::TestLog::Message << "Querying GL_SAMPLES to buffer with bufSize=2." << tcu::TestLog::EndMessage;
			gl.getInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_SAMPLES, 2, buffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_SAMPLES");

			if (buffer[2] != defaultValue)
			{
				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Query wrote over buffer bounds." << tcu::TestLog::EndMessage;
				error = true;
			}
		}

		// Query to empty buffer
		{
			glw::GLint buffer[1] = { defaultValue };

			m_testCtx.getLog() << tcu::TestLog::Message << "Querying GL_SAMPLES to zero-sized buffer." << tcu::TestLog::EndMessage;
			gl.getInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_SAMPLES, 0, buffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_SAMPLES");

			if (buffer[0] != defaultValue)
			{
				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Query wrote over buffer bounds." << tcu::TestLog::EndMessage;
				error = true;
			}
		}
	}

	// Result
	if (!error)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected buffer modification");

	return STOP;
}

} // anonymous

InternalFormatQueryTests::InternalFormatQueryTests (Context& context)
	: TestCaseGroup(context, "internal_format", "Internal format queries")
{
}

InternalFormatQueryTests::~InternalFormatQueryTests (void)
{
}

void InternalFormatQueryTests::init (void)
{
	static const struct InternalFormat
	{
		const char*						name;
		glw::GLenum						format;
		FormatSamplesCase::FormatType	type;
	} internalFormats[] =
	{
		// color renderable
		{ "r8",						GL_R8,					FormatSamplesCase::FORMAT_COLOR			},
		{ "rg8",					GL_RG8,					FormatSamplesCase::FORMAT_COLOR			},
		{ "rgb8",					GL_RGB8,				FormatSamplesCase::FORMAT_COLOR			},
		{ "rgb565",					GL_RGB565,				FormatSamplesCase::FORMAT_COLOR			},
		{ "rgba4",					GL_RGBA4,				FormatSamplesCase::FORMAT_COLOR			},
		{ "rgb5_a1",				GL_RGB5_A1,				FormatSamplesCase::FORMAT_COLOR			},
		{ "rgba8",					GL_RGBA8,				FormatSamplesCase::FORMAT_COLOR			},
		{ "rgb10_a2",				GL_RGB10_A2,			FormatSamplesCase::FORMAT_COLOR			},
		{ "rgb10_a2ui",				GL_RGB10_A2UI,			FormatSamplesCase::FORMAT_INT			},
		{ "srgb8_alpha8",			GL_SRGB8_ALPHA8,		FormatSamplesCase::FORMAT_COLOR			},
		{ "r8i",					GL_R8I,					FormatSamplesCase::FORMAT_INT			},
		{ "r8ui",					GL_R8UI,				FormatSamplesCase::FORMAT_INT			},
		{ "r16i",					GL_R16I,				FormatSamplesCase::FORMAT_INT			},
		{ "r16ui",					GL_R16UI,				FormatSamplesCase::FORMAT_INT			},
		{ "r32i",					GL_R32I,				FormatSamplesCase::FORMAT_INT			},
		{ "r32ui",					GL_R32UI,				FormatSamplesCase::FORMAT_INT			},
		{ "rg8i",					GL_RG8I,				FormatSamplesCase::FORMAT_INT			},
		{ "rg8ui",					GL_RG8UI,				FormatSamplesCase::FORMAT_INT			},
		{ "rg16i",					GL_RG16I,				FormatSamplesCase::FORMAT_INT			},
		{ "rg16ui",					GL_RG16UI,				FormatSamplesCase::FORMAT_INT			},
		{ "rg32i",					GL_RG32I,				FormatSamplesCase::FORMAT_INT			},
		{ "rg32ui",					GL_RG32UI,				FormatSamplesCase::FORMAT_INT			},
		{ "rgba8i",					GL_RGBA8I,				FormatSamplesCase::FORMAT_INT			},
		{ "rgba8ui",				GL_RGBA8UI,				FormatSamplesCase::FORMAT_INT			},
		{ "rgba16i",				GL_RGBA16I,				FormatSamplesCase::FORMAT_INT			},
		{ "rgba16ui",				GL_RGBA16UI,			FormatSamplesCase::FORMAT_INT			},
		{ "rgba32i",				GL_RGBA32I,				FormatSamplesCase::FORMAT_INT			},
		{ "rgba32ui",				GL_RGBA32UI,			FormatSamplesCase::FORMAT_INT			},

		// float formats
		{ "r16f",					GL_R16F,				FormatSamplesCase::FORMAT_COLOR			},
		{ "rg16f",					GL_RG16F,				FormatSamplesCase::FORMAT_COLOR			},
		{ "rgba16f",				GL_RGBA16F,				FormatSamplesCase::FORMAT_COLOR			},
		{ "r32f",					GL_R32F,				FormatSamplesCase::FORMAT_INT			},
		{ "rg32f",					GL_RG32F,				FormatSamplesCase::FORMAT_INT			},
		{ "rgba32f",				GL_RGBA32F,				FormatSamplesCase::FORMAT_INT			},
		{ "r11f_g11f_b10f",			GL_R11F_G11F_B10F,		FormatSamplesCase::FORMAT_COLOR			},

		// depth renderable
		{ "depth_component16",		GL_DEPTH_COMPONENT16,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
		{ "depth_component24",		GL_DEPTH_COMPONENT24,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
		{ "depth_component32f",		GL_DEPTH_COMPONENT32F,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
		{ "depth24_stencil8",		GL_DEPTH24_STENCIL8,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},
		{ "depth32f_stencil8",		GL_DEPTH32F_STENCIL8,	FormatSamplesCase::FORMAT_DEPTH_STENCIL	},

		// stencil renderable
		{ "stencil_index8",			GL_STENCIL_INDEX8,		FormatSamplesCase::FORMAT_DEPTH_STENCIL	}
		// DEPTH24_STENCIL8,  duplicate
		// DEPTH32F_STENCIL8  duplicate
	};

	static const struct
	{
		const char*	name;
		deUint32	target;
	} textureTargets[] =
	{
		{ "renderbuffer",					GL_RENDERBUFFER					},
		{ "texture_2d_multisample",			GL_TEXTURE_2D_MULTISAMPLE		},
		{ "texture_2d_multisample_array",	GL_TEXTURE_2D_MULTISAMPLE_ARRAY	},
	};

	for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(textureTargets); ++groupNdx)
	{
		tcu::TestCaseGroup* const	group		= new tcu::TestCaseGroup(m_testCtx, textureTargets[groupNdx].name, glu::getInternalFormatTargetName(textureTargets[groupNdx].target));
		const glw::GLenum			texTarget	= textureTargets[groupNdx].target;

		addChild(group);

		for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(internalFormats); ++caseNdx)
		{
			const std::string name = std::string(internalFormats[caseNdx].name) + "_samples";
			const std::string desc = std::string("Verify GL_SAMPLES of ") + internalFormats[caseNdx].name;

			group->addChild(new FormatSamplesCase(m_context, name.c_str(), desc.c_str(), texTarget, internalFormats[caseNdx].format, internalFormats[caseNdx].type));
		}
	}

	// Check buffer sizes are honored
	{
		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "partial_query", "Query data to too short a buffer");

		addChild(group);

		group->addChild(new NumSampleCountsBufferCase	(m_context, "num_sample_counts",	"Query GL_NUM_SAMPLE_COUNTS to too short a buffer"));
		group->addChild(new SamplesBufferCase			(m_context, "samples",				"Query GL_SAMPLES to too short a buffer"));
	}
}

} // Functional
} // gles31
} // deqp
